package edu.stanford.bmir.protege.web.server.stkosutil.mapper;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.Stack;

import org.protege.editor.owl.model.hierarchy.OWLObjectPropertyHierarchyProvider;
import org.semanticweb.owlapi.model.AddAxiom;
import org.semanticweb.owlapi.model.EntityType;
import org.semanticweb.owlapi.model.OWLAnnotation;
import org.semanticweb.owlapi.model.OWLAxiom;
import org.semanticweb.owlapi.model.OWLClass;
import org.semanticweb.owlapi.model.OWLDataFactory;
import org.semanticweb.owlapi.model.OWLEntity;
import org.semanticweb.owlapi.model.OWLLiteral;
import org.semanticweb.owlapi.model.OWLObjectProperty;
import org.semanticweb.owlapi.model.OWLOntologyChange;
import org.semanticweb.owlapi.model.RemoveAxiom;
import org.semanticweb.owlapi.vocab.OWLRDFVocabulary;

import edu.stanford.bmir.protege.web.client.rpc.data.EntityData;
import edu.stanford.bmir.protege.web.client.rpc.stkosutil.common.MetaItemList;
import edu.stanford.bmir.protege.web.client.rpc.stkosutil.common.UtilConstants;
import edu.stanford.bmir.protege.web.client.rpc.stkosutil.mapper.MappingOptions;
import edu.stanford.bmir.protege.web.client.rpc.stkosutil.mapper.MappingResult;
import edu.stanford.bmir.protege.web.server.MetaProjectManager;
import edu.stanford.bmir.protege.web.server.owlapi.AssertedClassHierarchyProvider;
import edu.stanford.bmir.protege.web.server.owlapi.OWLAPIProject;
import edu.stanford.bmir.protege.web.server.owlapi.OWLAPIProjectManager;
import edu.stanford.bmir.protege.web.server.owlapi.OntologyServiceOWLAPIImpl;
import edu.stanford.bmir.protege.web.server.owlapi.RenderingManager;
import edu.stanford.bmir.protege.web.shared.project.ProjectId;
import edu.stanford.bmir.protege.web.shared.user.UserId;

public class ObjectPropertyMapper {
	 final public String FLAG_MAPPING_NO="mappingNo";
	 /****
	  *The following functions are copied from OntologyServiceOWLAPIImpl as they are not visible
	  * 
	  */
	int currentMappingNo = 0;
	public int getCurrentMappingNo(){
		return this.currentMappingNo;
	}
	
	 private OWLAPIProject getProject(String projectName) {
	        if (projectName == null) {
	            throw new NullPointerException("projectName must not be null");
	        }
	        ProjectId projectId = ProjectId.get(projectName);
	        // TODO: Log
	        return getProject(projectId);
	    }

	    /**
	     * Gets the OWLAPIProject for a given {@link ProjectId}.If a project with the specified id exists then that project
	     * will be returned, otherwise, a fresh project will be created and that fresh project returned.
	     * @param projectId The id of the project.
	     * @return The OWL API project. Not <code>null</code>.
	     */
	    private OWLAPIProject getProject(ProjectId projectId) {
	        OWLAPIProjectManager pm = OWLAPIProjectManager.getProjectManager();
	        return pm.getProject(projectId);
	    }
	 //*******************   
	
	    
	 private List<OwlPropertyHolder> getSubProperties(OWLAPIProject project, String propertyName) {
	        if (propertyName == null) {
	            return Collections.emptyList();
	        }
	        List<OwlPropertyHolder> result = new ArrayList<OwlPropertyHolder>();
	        RenderingManager rm = project.getRenderingManager();
	        OWLObjectPropertyHierarchyProvider hierarchyProvider = project.getObjectPropertyHierarchyProvider();
	        OWLObjectProperty property = rm.getEntity(propertyName, EntityType.OBJECT_PROPERTY);
	        
	        for (OWLObjectProperty subProperty : new ArrayList<OWLObjectProperty>(hierarchyProvider.getChildren(property))) {
	                String browserText = rm.getBrowserText(subProperty);
	                String name = subProperty.getIRI().toString();
	                OwlPropertyHolder propertyHolder = new OwlPropertyHolder();;
	                propertyHolder.setBrowserText(browserText);
	                propertyHolder.setName(name);
	                propertyHolder.setOwlObjectProperty(subProperty);
	                result.add(propertyHolder);
//	            }
	        }
	        Collections.sort(result, new Comparator<OwlPropertyHolder>() {
	            public int compare(OwlPropertyHolder o1, OwlPropertyHolder o2) {
	                String browserText1 = o1.getBrowserText();
	                String browserText2 = o2.getBrowserText();
	                if(browserText1.startsWith("'")) {
	                    browserText1 = browserText1.substring(1);
	                }
	                if(browserText2.startsWith("'")) {
	                    browserText2 = browserText2.substring(1);
	                }
	                return browserText1.compareToIgnoreCase(browserText2);
	            }
	        });
	        return result;
	    }
	 	private List<OwlPropertyHolder> loadAllPropertyList(OWLAPIProject project){
	 		 List<OwlPropertyHolder> result = new ArrayList<OwlPropertyHolder>();
	 		 Stack<OwlPropertyHolder> sPropertyHolders = new Stack<OwlPropertyHolder>();
	 		 OwlPropertyHolder root = this.getRootProperty(project);
	 		 sPropertyHolders.push(root);
	 		 while (!sPropertyHolders.empty()){
	 			 OwlPropertyHolder currentProperty = sPropertyHolders.pop();
	 			 List<OwlPropertyHolder> children = this.getSubProperties(project, currentProperty.getName());
	 			 Collections.reverse(children);
	 			 for (OwlPropertyHolder child: children){
	 				 sPropertyHolders.push(child);
	 			 }
	 			 result.add(currentProperty);		 
	 		 }
	 		 return result;
	 	}
	 
	 	private OwlPropertyHolder getRootProperty(OWLAPIProject project) {
	 	     OwlPropertyHolder holder = new OwlPropertyHolder();
	 	     RenderingManager rm = project.getRenderingManager();
	 	     OWLObjectProperty topProperty = project.getDataFactory().getOWLTopObjectProperty();
	 	     String browserText = rm.getBrowserText(topProperty);
             String name = topProperty.getIRI().toString();
             holder.setBrowserText(browserText);
             holder.setName(name);
	 	     holder.setOwlObjectProperty(topProperty);
	 	     return holder;
	 	}
	 	public MappingResult mapOntologies(String project1Name, String project2Name, MappingOptions options, UserId userId){
	 		String project1DisplayName = "";
	 		String project2DisplayName = "";
	 		MetaProjectManager mpm = MetaProjectManager.getManager();
	 		project1DisplayName = mpm.getProjectDetails(ProjectId.get(project1Name)).getDisplayName();
	 		project2DisplayName  = mpm.getProjectDetails(ProjectId.get(project2Name)).getDisplayName();
	 		MappingResult result = new MappingResult();
	 		List<OwlPropertyHolder> o1PropertyList = null;
	 		List<OwlPropertyHolder> o2PropertyList = null;
	 		List<OWLOntologyChange> o1ChangeList = new ArrayList<OWLOntologyChange>();
	 		List<OWLOntologyChange> o2ChangeList = new ArrayList<OWLOntologyChange>();
	 		OWLAPIProject project1 = this.getProject(project1Name);
	 		OWLAPIProject project2 = this.getProject(project2Name);
	 		o1PropertyList= this.loadAllPropertyList(project1);
	 		o2PropertyList= this.loadAllPropertyList(project2);
	 		this.fillRemoveAxiom(project1, o1PropertyList, o1ChangeList, project2Name);
	 		this.fillRemoveAxiom(project2, o2PropertyList, o2ChangeList, project1Name);
	 		OwlPropertyHolderComparator comparator = new OwlPropertyHolderComparator(options.getMatchLevel());
	 		Collections.sort(o2PropertyList, comparator);
	 		for (OwlPropertyHolder o1Property: o1PropertyList){
	 			if (o1Property.getName().equals(OWLRDFVocabulary.OWL_TOP_OBJECT_PROPERTY.getIRI().toString())){
	 				continue;
	 			}
	 			int pos = Collections.binarySearch(o2PropertyList, o1Property, comparator);
	 			if (pos<0){
	 				continue;
	 			}
	 			ArrayList<OwlPropertyHolder> matchedPropertyList = new ArrayList<OwlPropertyHolder>();
	 			matchedPropertyList.add(o2PropertyList.get(pos));
	 			for (int i=pos+1; i<o2PropertyList.size(); i++){
	 				if (comparator.compare(o1Property, o2PropertyList.get(i))==0){  
	 					matchedPropertyList.add(o2PropertyList.get(i));
	 				}else{
	 					break;
	 				}
	 			}
	 			for (int i=pos-1; i>=0; i--){
	 				if (comparator.compare(o1Property, o2PropertyList.get(i))==0){
	 					matchedPropertyList.add(o2PropertyList.get(i));
	 				}else{
	 					break;
	 				}
	 			}
	 			List<Integer> existingMappingNoList = new ArrayList<Integer>();
		 		for (OwlPropertyHolder matchedProperty: matchedPropertyList){
		 			if (matchedProperty.getMappingNo()!=0){
		 				int index = Collections.binarySearch(existingMappingNoList,matchedProperty.getMappingNo());
	 		            if (index<0){
	 		            	existingMappingNoList.add((index + 1) * -1, matchedProperty.getMappingNo());
	 		            }
		 			}
		 		}
		 		if(existingMappingNoList.size()==0){
		 			currentMappingNo++;
		 			o1Property.setMappingNo(currentMappingNo);
		 			for (OwlPropertyHolder matchedProperty: matchedPropertyList){
		 				matchedProperty.setMappingNo(currentMappingNo);
		 			}
		 		}else{
		 			int preferredMappingNo= existingMappingNoList.get(0);
		 			o1Property.setMappingNo(preferredMappingNo);
		 			for (OwlPropertyHolder matchedProperty: matchedPropertyList){
		 				matchedProperty.setMappingNo(preferredMappingNo);
		 			}
		 			if (existingMappingNoList.size()>1){
			 			for (OwlPropertyHolder tempO1Property: o1PropertyList){
			 				for (int j=1; j<existingMappingNoList.size();j++){
			 					if (tempO1Property.getMappingNo()==existingMappingNoList.get(j).intValue()){
			 						tempO1Property.setMappingNo(preferredMappingNo);
			 					}
			 				}//end for
			 			}//end for
		 			}//end if
		 		}
	 		}//end for
	 		int o1MappedNumber = this.fillAddAxiom(project1, o1PropertyList, o1ChangeList, project2Name, project2DisplayName);
	 		int o2MappedNumber = this.fillAddAxiom(project2, o2PropertyList, o2ChangeList, project1Name, project1DisplayName);
	 		
	 		result.setO1MappedObjectPropertyNumber(o1MappedNumber);
	 		result.setO2MappedObjectPropertyNumber(o2MappedNumber);
	 		result.setO1ObjectPropertyNumber(o1PropertyList.size());
	 		result.setO2ObjectPropertyNumber(o2PropertyList.size());
	 		
	 		
	 		project1.applyChanges(userId, o1ChangeList, "本体映射");
	 		project2.applyChanges(userId, o2ChangeList, "本体映射");
	 		return result;
	 	}
	 	private void fillRemoveAxiom(OWLAPIProject project, List<OwlPropertyHolder> propertyList, List<OWLOntologyChange> changeList, String refProjectName){
	 		OWLDataFactory df = project.getDataFactory();
	 		for (OwlPropertyHolder owlPropertyHolder: propertyList){
	 			OWLObjectProperty owlProperty = owlPropertyHolder.getOwlObjectProperty(); 
	 			for (OWLAnnotation annotation : owlProperty.getAnnotations(project.getRootOntology(), df.getRDFSComment())) {
		    		 if (annotation.getValue() instanceof OWLLiteral) {
			    		 OWLLiteral commentValue = (OWLLiteral) annotation.getValue();
			    		 String value = commentValue.getLiteral();
			    		  if (MetaItemList.isPreProjectMappingFlag(value, refProjectName)){
			    			  OWLAxiom oldAx = df.getOWLAnnotationAssertionAxiom(
			    					  owlProperty.getIRI(), annotation);
			    			  RemoveAxiom removeAxiom = new RemoveAxiom(project.getRootOntology(),oldAx);
			    			  changeList.add(removeAxiom);
			    		  }
		    		 }//end if
	 			}//end for
	 			
	 		}
	 	}
	 	
	 	private int fillAddAxiom(OWLAPIProject project, List<OwlPropertyHolder> propertyList, List<OWLOntologyChange> changeList, String refProjectName, String refProjectDisplayName){
	 		int count=0;
	 		OWLDataFactory df = project.getDataFactory();
	 		for (OwlPropertyHolder owlPropertyHolder: propertyList){
	 			if (owlPropertyHolder.getMappingNo()==0){
	 				continue;
	 			}
	 			String annotaionValue= UtilConstants.FLAG_MAPPING_NO + ": " + owlPropertyHolder.getMappingNo() + "; "
	 					+ UtilConstants.FLAG_REF_PROJECT_DISPLAY_NAME + ": " + refProjectDisplayName + "; "
 	 					+ UtilConstants.FLAG_REF_PROJECT_ID+": " + refProjectName ;
	 			OWLObjectProperty owlClass = owlPropertyHolder.getOwlObjectProperty(); 
	 			OWLAnnotation commentAnno = df.getOWLAnnotation(
	 					 df.getRDFSComment(),
	 					 df.getOWLLiteral(annotaionValue, "en"));
	 			 OWLAxiom ax = df.getOWLAnnotationAssertionAxiom(
	 					 owlClass.getIRI(), commentAnno);
	 			 AddAxiom addAxiom =  new AddAxiom(project.getRootOntology(), ax);	 
	 			 changeList.add(addAxiom);
	 			 count++;
	 		}
	 		return count;
	 	}
	 	
}
